package cn.sola.rank;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.sola.util.DateUtil;

/**
 * @author: kangxiaohui
 * @date：2018年8月24日 下午3:28:26
 * @version 1.0 
 * redis 实现日排行榜
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-redis3.xml")
public class DayRank {

	private static final String base = "user:score";// key 的前缀
	
	private static final Long week_second = 7 * 24 * 60 * 60L;//默认过期时间为一周
	
	private static final String default_date_format = "yyyyMMdd";//默认过期时间为一周
	
	private static final Long MAX_TIME = 9999999999999L;
	
	
	@Resource(name = "redisTemplate")
	private ZSetOperations<String, String> zSet;
	
	@Test
	public void index(){
		System.out.println(zSet);
	}
	
	@Test
	public void addUserRecrd(){
		String value = "1231310123";
		Integer score = 1;
		String date = "2018-08-30";
		String key = base + ":" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		//排行真实的积分
		zSet.incrementScore(key, value, score);
		double storageScore = getScore(score);
		String scoreKey = base + ":score:" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		//排行
		zSet.incrementScore(scoreKey, value, storageScore);
	}
	
	/**
	 * 每天用户排行榜 聚合数据
	 * @param date
	 */
	@Test
	public void countUserDayRank(){
		String date = "2018-08-30";
		String key = base + ":" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		//积分数据
		zSet.unionAndStore("1", key, getDayKey(date).get("dayKey"));
		
		String scoreKey = base + ":score:" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		//积分排行
		zSet.unionAndStore("1", scoreKey, getDayKey(date).get("dayScoreKey"));
	}
	
	/**
	 * 查询日排行榜
	 */
	@Test
	public void getDayRank(){
		String date = "2018-08-30";
		String key = base + ":" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		System.out.println("-----------key 积分-----------------");
		circulation(zSet.reverseRangeWithScores(key, 0, -1));
		String scoreKey = base + ":score:" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		System.out.println("-----------key 积分排行-----------------");
		circulation(zSet.reverseRangeWithScores(scoreKey, 0, -1));
		
	}
	
	@Test
	public void getDayRankByScore(){
		String date = "2018-08-30";
		String scoreKey = base + ":score:" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		System.out.println("-----------key 积分排行-----------------");
		Iterator<TypedTuple<String>> iterator = zSet.reverseRangeWithScores(scoreKey, 0, -1).iterator();
        while(iterator.hasNext()){
            TypedTuple<String> next = iterator.next();
            System.out.println("value:"+next.getValue()+" score:"+next.getScore() + " rank: " + getRank(next.getScore()));
        }
	}
	
	private static Map<String, String> getDayKey(String date){
		String scoreKey  = base + ":score:day:rank:" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		String key = base + ":day:rank:" + DateUtil.dateToString(DateUtil.getDate(date),default_date_format);
		Map<String, String> map = new HashMap<>();
		map.put("dayKey", key);
		map.put("dayScoreKey", scoreKey);
		return map;
	}
	
	@Test
	public void testScore() throws InterruptedException{
		for(int index = 1; index < 100; index ++){
			Thread.sleep(1L);
			System.out.println(getScore(1));
		}
	}
	
	/**
	 * 循环输出数据
	 * @param tuples
	 */
	private void circulation(Set<TypedTuple<String>> tuples){
		Iterator<TypedTuple<String>> iterator = tuples.iterator();
        while(iterator.hasNext()){
            TypedTuple<String> next = iterator.next();
            System.out.println("value:"+next.getValue()+" score:"+next.getScore());
        }
	}
	
	/**
	 * 排名分数
	 * 不适合累加的操作
	 * @param rank 等级
	 * @return
	 */
	private double getScore(Integer rank){
		return (Math.pow(10, 14) * rank + MAX_TIME - System.currentTimeMillis());
	}
	
	/**
	 * 根据分数 计算排名
	 * @param score
	 * @return
	 */
	private Integer getRank(double score){
		return (int) (score / Math.pow(10, 14));
	}
	
	/**
	 * 
	 * 
	 * 
	 * 2) 设计方式二    

         long 整数长度总共有 19位，923XXX.......，时间戳 毫秒精度 是 13位，所以只需 14 ~ 19 位存 等级，其他13位存时间。接下来看怎么存。

                          等级偏移： Math.power(10, 14) = 10000000000000000（14位）

                           这里有一个最大时间 MAX_TIME = 9999999999999 （13位）

         A 玩家，（10 * 等级偏移） + MAX_TIME - 11111111111111（ 时间戳），最终分数 10888888888888888

         B 玩家，（10 * 等级偏移） + MAX_TIME - 22222222222222（ 时间戳），最终分数 10777777777777777

         

                          最终排序，A 玩家依然是第一。通过分数可以解析出真实 【等级 = 分数 / 等级偏移，取整】



        3. 劣势

        1) 如果有三个，四个排序条件怎么办，这种情况还是推荐使用数据库，就别考虑 Redis了 。Redis 优势在于可以做到实时排行

        2) 方式二 14 ~ 19位，那么等级最大数据就只能是 919999，超过这个数就会溢出。可以把时间戳降低到秒级别，可以支持更大数字
	 */
}
